package sms;

import sms.constant.EnrollmentStatus;

import java.io.*;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.*;


/*
1.该方法可以从一个小学生信息.txt(每行包含学生的学号,姓名,语文分数,数学分数,入学日期,邮箱)中将学生信息加载到学生信息管理系统中
2.该方法也可以从一个中学生信息.txt(每行包含学生学号,姓名,语文分数,数学分数,历史分数,物理分数,入学日期,邮箱)中将学生信息加载到学生信息管理系统中
3.根据文本文件的名称来判断是读取的小学生信息还是中学生信息
4.直接使用throws来处理异常,不使用try..catch
*/
//使用Collection和泛型重构学生信息管理系统
public class StudentManagementSystem03 {
    private ArrayList<Student> students = new ArrayList<>();

    /**
     * @param student      指定的学生对象
     * @param propertyName 指定的学生对象的属性
     */
    public void setPropertyValue(Student student, String propertyName,Object value) throws NoSuchElementException, IllegalAccessException, NoSuchFieldException {
        //1.获取字节码对象
        Class<?> cls = student.getClass();
        System.out.println(cls);

        Field field = null;
        while (cls != null) {//如果cls为null,说明已经找到Object类
            try {
                field = cls.getDeclaredField("name");//先获取本字节码中是否有该属性
                break;//如果获取到指定属性,就立马终止循环
            } catch (NoSuchFieldException e) {
                //如果执行catch说明在当前字节码中没有找到对应的属性,此时获取父类的字节码
                cls = cls.getSuperclass();
            }
        }
        //当循环执行完毕会出现两种情况
        //1.找到,field被赋值,通过break中断的循环
        //2.没找到,从当前字节码开始=>找父类的字节码=>...=>一直找到Object类字节码都没找到
        if (field == null) {
            throw new NoSuchFieldException("整个体系都找不到该属性：" + propertyName);
        }

        //2.跳过java的权限检查
        field.setAccessible(true);

        //3.设置指定的学生对象的属性值
        field.set(student,value);
    }

    /**
     *
     * @param student 学生对象
     * @param propertyName 指定的属性名
     * @return 该学生对象对应的属性值
     */
    public Object getPropertyValue(Student student,String propertyName) throws NoSuchFieldException, IllegalAccessException {
        //1.获取字节码对象
        Class<?> cls = student.getClass();
        System.out.println(cls);

        Field field = null;
        while (cls != null) {//如果cls为null,说明已经找到Object类
            try {
                field = cls.getDeclaredField(propertyName);//先获取本字节码中是否有该属性
                break;//如果获取到指定属性,就立马终止循环
            } catch (NoSuchFieldException e) {
                //如果执行catch说明在当前字节码中没有找到对应的属性,此时获取父类的字节码
                cls = cls.getSuperclass();
            }
        }
        //当循环执行完毕会出现两种情况
        //1.找到,field被赋值,通过break中断的循环
        //2.没找到,从当前字节码开始=>找父类的字节码=>...=>一直找到Object类字节码都没找到
        if (field == null) {
            throw new NoSuchFieldException("整个体系都找不到该属性：" + propertyName);
        }

        //2.跳过java的权限检查
        field.setAccessible(true);

        //3.获取指定的学生对象的属性值
        return field.get(student);
    }


    /**
     * @param resultPath 指定写入的目的地
     */
    public void writeRankedStudentsToFile(String resultPath) throws IOException {
        sortByTotalScore();

        BufferedWriter bw = new BufferedWriter(new FileWriter(resultPath));
        for (Student student : students) {
            bw.write(student.toString());
            bw.newLine();
        }

        bw.close();
    }


    /**
     *
     * @param filePath 数据源地址,例如:小学生信息.txt的路径
     */
    public void loadStudentsFromFile(String filePath) throws IOException {
        File file = new File(filePath);
        BufferedReader br = new BufferedReader(new FileReader(file));

        String line;
        while ((line = br.readLine()) != null) {
            String[] data = line.split(",");
            Integer studentId = Integer.parseInt(data[0]);
            String name = data[1];
            Integer chineseScore = Integer.parseInt(data[2]);
            Integer mathScore = Integer.parseInt(data[3]);
            LocalDate enrollmentDates = LocalDate.parse(data[4]);
            String email = data[5];
            EnrollmentStatus enrollmentStatus = EnrollmentStatus.getEnumByDesc(data[6]);

            if (file.getName().equals("中学生信息02.txt")) {
                Integer historyScore = Integer.parseInt(data[7]);
                Integer physiceScore = Integer.parseInt(data[8]);

                MiddleStudent middleStudent = new MiddleStudent(studentId, name, chineseScore, mathScore
                        , enrollmentDates, email, enrollmentStatus, historyScore, physiceScore);
                students.add(middleStudent);
            } else if (file.getName().equals("小学生信息02.txt")) {
                PrimaryStudent primaryStudent = new PrimaryStudent(studentId, name, chineseScore, mathScore
                        , enrollmentDates, email, enrollmentStatus);
                students.add(primaryStudent);
            }
        }
        br.close();
    }


    /**
     * 模拟上传学生头像
     *
     * @param localFilePath 本地硬盘路径
     * @param serverPath 服务器路径
     */
    public void uploadStudentAvatar(Student student, String localFilePath, String serverPath)throws Exception {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(localFilePath));

        String extension = localFilePath.substring(localFilePath.lastIndexOf("."));
        String uniqueFileName = UUID.randomUUID() + extension;
        serverPath = serverPath + "/" + uniqueFileName;

        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(serverPath));

        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }

        bis.close();
        bos.close();

        student.setAvatar(serverPath);


    }

    /**
     * 统计指定科目下不同成绩的学生人数
     * @param subject 指定科目
     * @return 不同成绩的学生人数统计
     */
    public Map<Integer, Integer> getScoreStatisticsBySubject(String subject){
        HashMap<Integer, Integer> scoreStatistics = null;
        try {
            scoreStatistics = new HashMap<>();
            for(Student student:students){
                int score = getScoreBySubject(student, subject);
                if(scoreStatistics.containsKey(score)){
                    scoreStatistics.put(score, scoreStatistics.get(score) + 1);
                }else {
                    scoreStatistics.put(score, 1);
                }
            }
        } catch (Exception e) {
            System.out.println("处理学生成绩出错:"+e.getMessage());
            e.printStackTrace();
        }
        return scoreStatistics;
    }

    /**
     * @param subject 指定科目,例如: "语文"
     * @return 指定科目最高成绩的学生信息
     */
    public Student findStudentWithHighestScore(String subject){
        try {
            return Collections.max(students, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return getScoreBySubject(o1, subject) - getScoreBySubject(o2, subject);
                }
            });
        } catch (NoSuchElementException e) {
            System.out.println("没有找到任何学生:"+e.getMessage());
            e.printStackTrace();
            return null;//执行catch里面说明出问题,直接返回null;
        }catch (ClassCastException e){
            System.out.println("学生的分数不可比较:"+e.getMessage());
            e.printStackTrace();
            return null;//执行catch里面说明出问题,直接返回null;
        }
    }


    /**
     * @param subject 指定科目,例如: "语文"
     * @return 指定科目最低成绩的学生信息
     */
    public Student findStudentWithLowestScore(String subject){
        try {
            return Collections.min(students, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return getScoreBySubject(o1, subject) - getScoreBySubject(o2, subject);
                }
            });
        } catch (NoSuchElementException e) {
            System.out.println("没有找到任何学生:"+e.getMessage());
            e.printStackTrace();
            return null;//执行catch里面说明出问题,直接返回null;
        }catch (ClassCastException e){
            System.out.println("学生的分数不可比较:"+e.getMessage());
            e.printStackTrace();
            return null;//执行catch里面说明出问题,直接返回null;
        }
    }


    /**
     * 根据每个学生的总分数进行成绩从
     */
    public void sortByTotalScore(){
        /*//1.构建一个TreeSet并且利用比较器指定比较规则
        TreeSet<Student> temps = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //如果两个学生总分相同,但是姓名不同 导致另外一个学生无法存入TreeSet
                // 会导致   张三 158  李四 158,只存储一个人
                //return o2.getTotalScore() - o1.getTotalScore();
                int result = o2.getTotalScore() - o1.getTotalScore();
                if (result == 0) {
                    int nameResult = o2.getName().compareTo(o1.getName());
                    if (nameResult == 0) {
                        //如果总分相同,再去比较姓名
                        //比较总分和姓名可能出现 总分相同 姓名也相同
                        //例如: 2021137 王五 158  2021128 王五 158
                        return o2.getStudentId() - o1.getStudentId();
                    } else {
                        //总分相同,但是姓名不同
                        return nameResult;
                    }
                } else {
                    return result; //总分不同,不再比较次要条件
                }
            }
        });

        temps.addAll(students);//将指定的集合中的元素拷贝到当前集合,相当于将ArrayList中的元素拷贝到TreeSet中
        students.clear();//清空系统中原有的元素
        students.addAll(temps);//再把TreeSet中按照总分排好序的元素放回ArrayList中*/

        try {
            students.sort(new Comparator<Student>() {
                //JDK8以后的版本可用
                @Override
                public int compare(Student o1, Student o2) {
                    return o2.getTotalScore() - o1.getTotalScore();//List集合允许存重复元素,因此不需要比较次要条件,所有的元素都会存入
                }
            });
        } catch (Exception e) {
            System.out.println("排序过程中出错:"+e.getMessage());
            e.printStackTrace();
        }
    }
    /**
     *
     * @param number 学生的编号(从1开始)
     * @param newStudent 学生的新的信息(学生对象中封装新的信息)
     * @return 更改前的学生对象
     */
    public Student updateStudentByIndex(int number, Student newStudent) {
        try {
            if (number >= 1 && number <= students.size()) {
                return students.set(number - 1, newStudent);
            }else{
               /* System.out.println("不存在序号为:" + number + "的学生信息");
                return null;*/
                throw new RuntimeException("指定学生的序号超出范围:" + number);
            }
        } catch (RuntimeException e) {
            System.out.println("更新学生信息时出错:"+e.getMessage());//更新学生信息时出错:指定学生的序号超出范围:10
            e.printStackTrace();
            return null;
        }
    }


    /**
     *
     * @return 随机取到的学生
     */
    public Student randomRollCall(){
/*        Random random = new Random();
        int randomindex = random.nextInt(students.size());
        return students.get(randomindex);*/
        try {
            return students.get(new Random().nextInt(students.size()));
        } catch (Exception e) {
            System.out.println("随机点名时出错:"+e.getMessage());
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 添加学生
     * @param student
     */
    public void addStudent(Student student){
        try {
            if (student != null && validateStudentEmail(student)) {
                students.add(student);
            }
        } catch (Exception e) {
            System.out.println("添加学生信息时出错:"+e.getMessage());
            e.printStackTrace();
        }
    }


    /**
     *
     * @param student 学生对象
     * @return 学生的邮箱校验结果
     */
    public boolean validateStudentEmail(Student student){
        if(student==null||student.getEmail()==null){
            throw new RuntimeException("学生为null或者邮箱为null");
        } else {
            return student.getEmail().matches("\\w+@\\w+\\.\\w+");
        }
    }


    /**
     * 遍历指定容器中的学生
     * @param students 指定的学生容器
     */
    public void showAllStudents(Collection<Student> students){
        try {
            int i = 1;
            for (Student student:students) {
                //if (students != null) 不用再做健壮性判断,添加学生时已经做了
                    System.out.println((i++) + " " + student);
            }
        } catch (Exception e) {
            System.out.println("展示学生信息时出错:"+e.getMessage());
            e.printStackTrace();
        }
    }


    /**
     * 遍历容器中学生
     */
    public void showAllStudents(){
        showAllStudents(students);
    }


    /**
     * 判断系统中是否存在该学生(要求如果系统中存在该学生的姓名,我们就认为该学生在系统存在)
     */
    public boolean contains(Student stu){
        try {
            return students.contains(stu);
        } catch (Exception e) {
            throw new RuntimeException(e);//将异常对象进一步封装,将来可以打印出e的信息
        }
    }


    /**
     * @param studentId 学生学号
     * @return 根据学生学号, 查询到的学生信息(封装到学生对象中)
     */
    public Student findStudentById(int studentId){
        try {
            for (Student student:students) {
                if ( student.getStudentId() == studentId) {
                    return student;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }


    /**
     * @param subject 指定科目 例如:数学
     * @return 所有学生指定科目的平均成绩
     */
    public int getAverageScoreBySubject(String subject){
        try {
            int totalScore = 0;
            for (Student student:students) {
                    int score = getScoreBySubject(student, subject);
                    totalScore += score;
            }
            return students.size() == 0 ? 0 : totalScore / students.size();
        } catch (Exception e) {
            throw new RuntimeException("计算平均分时出错",e);
        }
    }


    /**
     * @param student 学生对象
     * @param subject 科目名称
     * @return 当前学生指定科目的成绩
     */
    private int getScoreBySubject(Student student, String subject) {
        try {
            switch (subject){
                case "语文" -> {
                    return student.getChineseScore();
                }
                case "数学" ->{
                    return student.getMathScore();
                }
                case "历史" ->{
                    if(student instanceof MiddleStudent middleStudent){//历史物理成绩为中学生特有，用instanceof关键词判断是否为中学生在返回成绩
                        return ((MiddleStudent) student).getHistoryScore();
                    }
                }
                case "物理" ->{
                    if(student instanceof MiddleStudent middleStudent){
                        return ((MiddleStudent) student).getPhysicsScore();
                    }
                }

            }
            return 0;
        } catch (Exception e) {
            throw new RuntimeException("获取当前学生:" + student + "的" + subject + "时出错", e);
        }
    }


    /**
     *
     * @param date 指定日期
     * @return 存储了满足条件的学生信息的集合
     */
    public Collection<Student> getStudentsEnrolledBefore(String date){
        Collection<Student> temps = null;
        try {
            //1.定义一个集合来存放满足条件的学生
            temps = new ArrayList<>();

            //2.遍历students中的每个学生,逐个判断
            //如果该学生的入学日期在指定日期之前,我们就将该学生存入temps数组中
            for (Student student:students) {
                if (student.getEnrollmentDates() != null) {
                    if (student.getEnrollmentDates().isBefore(LocalDate.parse(date)) || student.getEnrollmentDates().isEqual(LocalDate.parse(date))) {
                        temps.add(student);
                    }
                }
            }
        } catch (DateTimeParseException e) {
            System.out.println("解析日期" + date + "时出错:" + e.getMessage());
        }catch (Exception e){
            throw new RuntimeException("获取指定入学日期值之前入学的学生出错:", e);
        }
        return temps;
    }



    /**
     *
     * @param date 指定日期
     * @return 存储了满足条件的学生信息的集合
     */
    public Collection getStudentsEnrolledAfter(String date){
        Collection<Student> temps = null;
        try {
            //1.定义一个集合来存放满足条件的学生
            temps = new ArrayList();

            //2.遍历students中的每个学生,逐个判断
            //如果该学生的入学日期在指定日期之前,我们就将该学生存入temps数组中
            for (Student student:students) {
                if (student.getEnrollmentDates() != null) {
                    if (student.getEnrollmentDates().isAfter(LocalDate.parse(date)) || student.getEnrollmentDates().isEqual(LocalDate.parse(date))) {
                        temps.add(student);
                    }
                }
            }
        } catch (DateTimeParseException e) {
            System.out.println("解析日期" + date + "时出错:" + e.getMessage());
        }catch (Exception e){
            throw new RuntimeException("获取指定入学日期值之后入学的学生出错:", e);
        }
        return temps;
    }
}
